﻿/*
 * @author: wizardc
 */

using System;
using System.Collections.Generic;
using System.IO;
using Dou.Clock;
using Dou.Timer;
using Dou.Utils;
using UnityEngine;
using Object = UnityEngine.Object;

namespace Dou.Resource
{
    /// <summary>
    /// 基于 AssetDatabase 的资源加载器
    /// </summary>
    internal class AssetBundleLoader : IAssetLoader, IClockClient
    {
        /// <summary>
        /// 资源配置相关文件名
        /// </summary>
        private const string resourceManifest = "rm.list";

        private bool _isEncrypt;
        private Dictionary<string, AssetUnloadSetting> _unloadSettingMap = new Dictionary<string, AssetUnloadSetting>();

        private AssetBundleLoadOperate _assetBundleLoad = new AssetBundleLoadOperate();
        private AssetLoadOperate _assetLoad = new AssetLoadOperate();

        private ResourceManifest _resourceManifest;

        private Dictionary<string, AssetBundleInfo> _assetBundleInfoMap = new Dictionary<string, AssetBundleInfo>();
        private List<AssetBundleInfo> _waitUnloadAB = new List<AssetBundleInfo>();

        public int maxLoadABThread
        {
            set => _assetBundleLoad.maxLoadThread = value;
            get => _assetBundleLoad.maxLoadThread;
        }
        
        public int maxLoadAssetThread
        {
            set => _assetLoad.maxLoadThread = value;
            get => _assetLoad.maxLoadThread;
        }

        public void Init(bool isEncrypt, Action onComplete, AssetUnloadSetting[] unloadSettings)
        {
            ClockUtil.realTimeClock.AddClient(this);
            foreach (var unloadSetting in unloadSettings)
            {
                _unloadSettingMap.Add(unloadSetting.abName, unloadSetting);
            }
            var resourceManifestPath = AssetUtil.GetFilePath(resourceManifest);
            AssetUtil.ReadFile(resourceManifestPath, bytes =>
            {
                _resourceManifest = AssetUtil.DecodeMessagePack<ResourceManifest>(bytes);
                onComplete();
            }, () =>
            {
                Log.Error($"资源加载异常：\"{resourceManifestPath}\"");
            });
        }

        public void Update(float passedTime)
        {
            foreach (var kv in _assetBundleInfoMap)
            {
                var assetBundleInfo = kv.Value;
                if (assetBundleInfo.state == AssetBundleState.Unloading)
                {
                    assetBundleInfo.unloadRemainingTime -= passedTime;
                    if (assetBundleInfo.unloadRemainingTime <= 0)
                    {
                        _waitUnloadAB.Add(assetBundleInfo);
                    }
                }
            }
            if (_waitUnloadAB.Count > 0)
            {
                foreach (var assetBundleInfo in _waitUnloadAB)
                {
                    assetBundleInfo.assetBundle.Unload(_unloadSettingMap[assetBundleInfo.abName].unloadAllLoadedObjects);
                    _assetBundleInfoMap.Remove(assetBundleInfo.abName);
                }
                _waitUnloadAB.Clear();
            }
        }

        public void LoadAssetBundle(string name, Action<AssetBundle> onComplete, int priority)
        {
            LoadAssetBundle(name, onComplete, priority, false);
        }

        public void LoadAssetBundleSync(string name, Action<AssetBundle> onComplete, int priority)
        {
            LoadAssetBundle(name, onComplete, priority, true);
        }

        private void LoadAssetBundle(string name, Action<AssetBundle> onComplete, int priority, bool isSync)
        {
            var assetBundleInfo = GetAssetBundleInfo(name);
            LoadAssetBundleSingle(assetBundleInfo, priority, isSync, onComplete);
            // 依赖项加载
            foreach (var dependent in assetBundleInfo.dependents)
            {
                var dependentInfo = GetAssetBundleInfo(dependent);
                LoadAssetBundleSingle(dependentInfo, priority, isSync);
            }
        }

        private AssetBundleInfo GetAssetBundleInfo(string abName)
        {
            abName = abName.ToLower();
            if (!_assetBundleInfoMap.ContainsKey(abName))
            {
                _assetBundleInfoMap.Add(abName, new AssetBundleInfo(abName, _resourceManifest.dependents[abName]));
            }
            return _assetBundleInfoMap[abName];
        }
        
        private void LoadAssetBundleSingle(AssetBundleInfo assetBundleInfo, int priority, bool isSync, Action<AssetBundle> onComplete = null)
        {
            var abName = assetBundleInfo.abName;
            switch (assetBundleInfo.state)
            {
                case AssetBundleState.None:
                {
                    AddRef(abName);
                    assetBundleInfo.state = AssetBundleState.Loading;
                    if (onComplete != null)
                    {
                        assetBundleInfo.completeHandler.Add(onComplete);
                    }
                    var fullPath = AssetUtil.GetFilePath(abName);
                    _assetBundleLoad.Load(fullPath, assetBundle =>
                    {
                        if (!_assetBundleInfoMap.ContainsKey(abName))
                        {
                            Log.Error($"AB包\"{abName}\"的信息异常丢失！");
                            return;
                        }
                        var assetBundleInfo = _assetBundleInfoMap[abName];
                        if (assetBundleInfo.state == AssetBundleState.Unloaded) // 加载过程中就被卸载的情况
                        {
                            assetBundle.Unload(true);
                            _assetBundleInfoMap.Remove(abName);
                            return;
                        }
                        assetBundleInfo.assetBundle = assetBundle;
                        assetBundleInfo.state = AssetBundleState.Complete;
                        LoadAssetBundleSingleComplete();
                    }, priority, isSync, _isEncrypt);
                    break;
                }
                case AssetBundleState.Loading:
                {
                    if (onComplete != null)
                    {
                        assetBundleInfo.completeHandler.Add(onComplete);
                    }
                    break;
                }
                case AssetBundleState.Complete:
                {
                    if (onComplete != null)
                    {
                        onComplete(assetBundleInfo.assetBundle);
                    }
                    break;
                }
                case AssetBundleState.Unloaded:
                {
                    AddRef(abName);
                    assetBundleInfo.state = AssetBundleState.Loading;
                    break;
                }
                case AssetBundleState.Unloading:
                {
                    AddRef(abName);
                    // 取消延时卸载
                    assetBundleInfo.state = AssetBundleState.Complete;
                    assetBundleInfo.unloadRemainingTime = 0f;
                    break;
                }
            }
        }

        private void LoadAssetBundleSingleComplete()
        {
            foreach (var kv in _assetBundleInfoMap)
            {
                var assetBundleInfo = kv.Value;
                if (assetBundleInfo.state == AssetBundleState.Complete && assetBundleInfo.completeHandler.Count > 0 && CheckAllLoaded(assetBundleInfo))
                {
                    foreach (var completeHandler in assetBundleInfo.completeHandler)
                    {
                        completeHandler(assetBundleInfo.assetBundle);
                    }
                    assetBundleInfo.completeHandler.Clear();
                }
            }
        }

        private bool CheckAllLoaded(AssetBundleInfo assetBundleInfo)
        {
            if (assetBundleInfo.dependents == null || assetBundleInfo.dependents.Length == 0)
            {
                return true;
            }
            for (int i = 0; i < assetBundleInfo.dependents.Length; i++)
            {
                var dependent = assetBundleInfo.dependents[i];
                if (!_assetBundleInfoMap.ContainsKey(dependent))
                {
                    Log.Error($"AB包\"{assetBundleInfo.abName}\"依赖项\"{dependent}\"缺失异常！");
                    return false;
                }
                var dependentInfo = _assetBundleInfoMap[dependent];
                if (dependentInfo.state != AssetBundleState.Complete)
                {
                    return false;
                }
            }
            return true;
        }

        public AssetBundle GetAssetBundle(string name)
        {
            name = name.ToLower();
            if (_assetBundleInfoMap.ContainsKey(name) && _assetBundleInfoMap[name].state == AssetBundleState.Complete)
            {
                return _assetBundleInfoMap[name].assetBundle;
            }
            return null;
        }

        public void UnloadAssetBundle(string name, bool force)
        {
            name = name.ToLower();
            if (!_assetBundleInfoMap.ContainsKey(name))
            {
                Log.Warning($"尝试卸载未加载的AB包\"{name}\"！");
                return;
            }

            var assetBundleInfo = _assetBundleInfoMap[name];
            UnloadAssetBundleSingle(assetBundleInfo, force);
            // 依赖项卸载
            foreach (var dependent in assetBundleInfo.dependents)
            {
                var dependentInfo = GetAssetBundleInfo(dependent);
                UnloadAssetBundleSingle(dependentInfo, force);
            }
        }

        public void UnloadAssetBundleSingle(AssetBundleInfo assetBundleInfo, bool force)
        {
            var abName = assetBundleInfo.abName;
            switch (assetBundleInfo.state)
            {
                case AssetBundleState.None:
                {
                    Log.Error($"卸载的AB包\"{abName}\"状态异常！");
                    break;
                }
                case AssetBundleState.Loading:
                {
                    DecRef(abName);
                    assetBundleInfo.state = AssetBundleState.Unloaded;
                    break;
                }
                case AssetBundleState.Complete:
                {
                    DecRef(abName);
                    var refCount = assetBundleInfo.refCount;
                    if (!force && refCount > 0)
                    {
                        Log.Warning($"卸载AB包\"{abName}\"失败，因为引用次数为\"{refCount}\"！");
                        return;
                    }
                    if (force && refCount > 0)
                    {
                        Log.Warning($"强制卸载了引用次数为\"{refCount}\"的AB包\"{abName}\"！");
                    }
                    UnloadAssetBundle(assetBundleInfo);
                    break;
                }
                case AssetBundleState.Unloaded:
                {
                    break;
                }
                case AssetBundleState.Unloading:
                {
                    break;
                }
            }
        }

        private void UnloadAssetBundle(AssetBundleInfo assetBundleInfo)
        {
            var abName = assetBundleInfo.abName;
            // 无特殊设定时的卸载
            if (!_unloadSettingMap.ContainsKey(abName))
            {
                assetBundleInfo.assetBundle.Unload(true);
                _assetBundleInfoMap.Remove(abName);
                return;
            }
            // 有特殊设定时的卸载
            var unloadSetting = _unloadSettingMap[abName];
            if (unloadSetting.type == AssetUnloadType.Never)
            {
                Log.Warning($"尝试卸载的AB包\"{abName}\"被标记为不被卸载！");
            }
            else if (unloadSetting.type == AssetUnloadType.Immediate)
            {
                assetBundleInfo.assetBundle.Unload(unloadSetting.unloadAllLoadedObjects);
                _assetBundleInfoMap.Remove(abName);
            }
            else
            {
                // 延迟卸载
                assetBundleInfo.state = AssetBundleState.Unloading;
                assetBundleInfo.unloadRemainingTime = unloadSetting.delayTime;
            }
        }
        
        public void AddRef(string abName, int count = 1)
        {
            abName = abName.ToLower();
            if (!_assetBundleInfoMap.ContainsKey(abName))
            {
                Log.Warning($"无效增加AB包\"{abName}\"引用次数！");
                return;
            }
            _assetBundleInfoMap[abName].refCount += count;
        }

        public void DecRef(string abName, int count = 1)
        {
            abName = abName.ToLower();
            if (!_assetBundleInfoMap.ContainsKey(abName))
            {
                Log.Warning($"无效减少AB包\"{abName}\"引用次数！");
                return;
            }
            _assetBundleInfoMap[abName].refCount -= count;
            if (_assetBundleInfoMap[abName].refCount < 0)
            {
                _assetBundleInfoMap[abName].refCount = 0;
                Log.Warning($"AB包\"{abName}\"引用次数被减少到0次以下！");
            }
        }

        public void AddAssetRef(string assetName, int count = 1)
        {
            assetName = assetName.ToLower();
            assetName = _resourceManifest.shortNames[assetName];
            var abName = _resourceManifest.belongs[assetName];
            AddRef(abName, count);
        }

        public void DecAssetRef(string assetName, int count = 1)
        {
            assetName = assetName.ToLower();
            assetName = _resourceManifest.shortNames[assetName];
            var abName = _resourceManifest.belongs[assetName];
            DecRef(abName, count);
        }

        public void LoadAsset(string name, Type type, Action<Object> onComplete, int priority)
        {
            name = name.ToLower();
            name = _resourceManifest.shortNames[name];
            var abName = _resourceManifest.belongs[name];
            LoadAssetBundle(abName, bundle =>
            {
                _assetLoad.LoadAsset(bundle, name, type, onComplete, priority, false);
            }, priority);
        }

        public void LoadAssetSync(string name, Type type, Action<Object> onComplete, int priority)
        {
            name = name.ToLower();
            name = _resourceManifest.shortNames[name];
            var abName = _resourceManifest.belongs[name];
            LoadAssetBundleSync(abName, bundle =>
            {
                _assetLoad.LoadAsset(bundle, name, type, onComplete, priority, true);
            }, priority);
        }

        public void UnloadAsset(string name)
        {
            name = name.ToLower();
            DecAssetRef(name, 1);
        }
        
        public void LoadPrefab(string name, Action<GameObject> onComplete, int priority)
        {
            LoadAsset(name, typeof(GameObject), obj =>
            {
                var go = GameObject.Instantiate(obj) as GameObject;
                onComplete(go);
            }, priority);
        }

        public void LoadPrefabSync(string name, Action<GameObject> onComplete, int priority)
        {
            LoadAssetSync(name, typeof(GameObject), obj =>
            {
                var go = GameObject.Instantiate(obj) as GameObject;
                onComplete(go);
            }, priority);
        }

        public void UnloadUnusedAB()
        {
            foreach (var kv in _assetBundleInfoMap)
            {
                var assetBundleInfo = kv.Value;
                if (assetBundleInfo.refCount == 0)
                {
                    _waitUnloadAB.Add(assetBundleInfo);
                }
            }
            if (_waitUnloadAB.Count > 0)
            {
                foreach (var assetBundleInfo in _waitUnloadAB)
                {
                    UnloadAssetBundle(assetBundleInfo);
                }
                _waitUnloadAB.Clear();
            }
        }

        public string GetAssetBundleName(string name)
        {
            name = name.ToLower();
            if (_resourceManifest.abName.ContainsKey(name))
            {
                return _resourceManifest.abName[name];
            }
            return name;
        }

        public string GetAssetFullPath(string name)
        {
            name = name.ToLower();
            if (_resourceManifest.shortNames.ContainsKey(name))
            {
                return _resourceManifest.shortNames[name];
            }
            return name;
        }

        public string GetRawAssetPath(string name)
        {
            name = name.ToLower();
            if (_resourceManifest.rawNames.ContainsKey(name))
            {
                return _resourceManifest.rawNames[name];
            }
            return name;
        }
    }
}
